import sys
import os

# First, the Requirement class contains requirement id
# (ie. REQ_EP_123), name, details (description), type (i.e. file, app,
# or def), category (e.g. track, compositional, anotation, etc) -
# everything reported by the analyzer (and documented in the
# Requirements.xml file).  This
#
# Second, the Requirement class stores a boolean flag indicating
# whether the requirement is covered by a test. This information is
# reported by the AAFAnalyzer and used to initialize Requirement
# object instances.
#
# Third, the Requirement class stores pass/warn/fail test results each
# file that has a test result reported against the requirement.

class Requirement:
		
	typeSortOrder = { "file":1, "app":2, "def":3 }

	def __init__(self, reqid):
		
		# The id of this requirement. ie. REQ_EP_123
		self.reqid   = reqid

		# Flag indicating if an AAFAnalyzer test exists for this requirement.
		self.covered = False

		# Dictionary that maps file name to a test result for
		# this requirement as reported by the analyzer.
		self.filecov ={}
	
	# Compare requiremens. Order by type, category, id,
	# and finally name. Types are compared such that file
	# requirements appear first in a requirement list.
	def __cmp__(self, other):

		c = cmp(self.get_is_covered_by_test(), other.get_is_covered_by_test())
		if c:
			return -1*c

		c = cmp(self.typeSortOrder[self.get_type()], self.typeSortOrder[other.get_type()])
		if c:
			return c

		c = cmp(self.get_id(), other.get_id())
		if c:
			return c

		c = cmp(self.get_name(), other.get_name())
		if c:
			return c

		c = cmp(self.get_category(), other.get_category())
		if c:
			return c

		return 0
	
	# Get a fill description fo the requirement.
	def get_all(self):
	
		all=self.get_id()+':  ' +self.get_name()+ '\n\n'
		all=all+'Description: ' +self.get_details() + '\n\n' 
		all=all+'Type:	      ' +self.get_type() + '\n'
		all=all+'Document:    ' +self.get_document() + '\n' 
		all=all+'Section:     ' +self.get_section() + '\n' 
		all=all+'Version:     ' +self.get_version() 
		
		return all
				
	def get_id(self):
		return self.reqid
	def set_id(self, req):
		self.reqid=req

	def get_name(self):
		return self.name
	def set_name(self, n):
		self.name=n
		
	def get_details(self):
		return self.details
	def set_details(self, det):
		self.details=det
	
	def get_category(self):
		return self.category
	def set_category(self, cat):
		self.category=cat
	
	def get_type(self):
		return self.typ
	def set_type(self, t):
		self.typ=t
	
	def get_document(self):
		return self.document
	def set_document(self, doc):
		self.document=doc
	
	def get_version(self):
		return self.version
	def set_version(self, ver):
		self.version=ver
		
	def get_section(self):
		return self.section
	def set_section(self, sec):
		self.section=sec
		
	def get_is_covered_by_test(self):
		return self.covered
	def set_is_covered_by_test(self, isCovered):
		self.covered = isCovered
	
	def get_file_test_result_for_file(self, fileName):
		return self.filecov[fileName]

	def test_result_exists_for_file(self, fileName):
		return fileName in self.filecov;
	
	def set_file_test_result_for_file(self, filename, testResult):
		self.filecov[filename] = testResult

# Parses requirement data from Analyzer
#
# Returns a two element tuple. The first element is an array of all
# requires. The second is an array of file requirements only.
#
# detailedReqListFile is an opened file descriptor from which the list
# of detailed requirements is read. This is typically could be
# AAFAnalyzer process, or it could be an opened file generated by the
# analyzer.
#
# coveredReqListFile is an opened file descriptor from which the list
# of requirements covered by a test implementation is read.  This is
# typically the AAFAnalyzer process, or it could be an opened file
# generated by the analyzer.
#
# Return value is a dictionary that of Requirement object instances
# keyed by their requirment id.  The basic requirement information
# (id, name type, etc) is initialized from the detailedReqListFile.
# Their test coverage is initialized from the coveredReqListFile.
# Both of these are the output of the AAFAnalyzer. The commands that
# generate this information are:
#
# detailedReqListFile:
#      AAFAnalyzer -report -reqs AAFRequirements.xml -detail -type all
#
# coveredReqlistFile:
#      AAFAnalyzer -report -reqs AAFRequirement.xml -testcoverage
#
# The results of these commands can be stored in files, or, can be
# piped directly into this function by passing stdout of the process
# to the file parameter.

def parse_requirements(detailedReqListFile, coveredReqListFile):

	# detailedReqList = All AAF Requirement details.
	# (i.e. output of AAFAnalyzer -report -req AAFRequirements.xml -detail)
	unparsedreqs = detailedReqListFile.readlines()

	# coveredReqListFile = All AAF Requirements covered by the AAFAnalyzer.
	# (i.e. output of AAFAnalyzer -report -req AAFRequirements.xml -coverage)
	testsuitecov = ''.join(coveredReqListFile.readlines())

	# Initialize dictionary to map requirement id to requirement
	# object instance.
	reqs={}
	req = None

	for line in unparsedreqs:

		if line.count('ID:') > 0:
			reqid = line.strip("ID:").strip()
			req = Requirement(reqid)
			reqs[reqid] = req
			# If the requirement appears in the list of the AAFAnalyzer's
			# covered requirements set as covered
			if reqid in testsuitecov:
				req.set_is_covered_by_test( True )
		elif line.count('Name:') > 0:
			req.set_name(line.strip("Name:").strip())
		elif line.count('Type:') > 0:
			req.set_type(line.strip("Type:").strip())
		elif line.count('Category:') > 0:
			req.set_category(line.strip("Category:").strip())
		elif line.count('Description:') > 0:
			req.set_details(line.strip("Description:").strip())
		elif line.count('Document:') > 0:
			req.set_document(line.strip("Document:").strip())
		elif line.count('Version:') > 0:
			req.set_version(line.strip("Version:").strip())
		elif line.count('Section:') > 0:
			req.set_section(line.strip("Section:").strip())

	return (reqs)

# This function parses requirement coverage data from file created by
# the analyzer.py script

def parse_file_coverage(filename, requirements):
	
	try:
		cfile=open(filename, 'r')
		line=cfile.readline()
		while line<>'':
			if 'File Set' in line or 'File Name:' in line:
				if 'File Set' in line:
					filen='File Set'
				else:
					if sys.platform == 'win32' or sys.platform == 'cygwin':
						filen=line.strip('File Name: ').strip().split('\\')[-1]
					else:
						filen=line.strip('File Name: ').strip().split('/')[-1]
			
			# Update the Covered/Noted/Pass/Fail/Warn
			# status on each requirement for each file via
			# a dictionary stored in the Requirement
			# object

			elif 'Covered Requirements:' in line and 'None' not in line:
				for req in line.strip().replace('Covered Requirements: ','').split('; '):
					requirements[req].set_file_test_result_for_file(filen,'Covered')
	
			elif 'Noted Requirements:' in line and 'None' not in line:
				for req in line.strip().replace('Noted Requirements: ','').split('; '):
					requirements[req].set_file_test_result_for_file(filen,'Noted')
	
			elif 'Passing Requirements:' in line and 'None' not in line:
				for req in line.strip().replace('Passing Requirements: ','').split('; '):
					requirements[req].set_file_test_result_for_file(filen,'Passed')
	
			elif 'Warning Requirements:' in line and 'None' not in line: 
				for req in line.replace('Warning Requirements:','').strip().split('; '):
					requirements[req].set_file_test_result_for_file(filen, 'Warned')
	
			elif 'Failing Requirements:' in line and 'None' not in line: 
				for req in line.replace('Failing Requirements:','').strip().split('; '):
					requirements[req].set_file_test_result_for_file(filen, 'Failed')

			line = cfile.readline()

		cfile.close()

	except IOError, e:
				print >>sys.stderr, "Error reading file: " +filename+" ", e

	
# Dumps all the data (requirement, coverage) into a tabular form.
#
# Builds a 2D table as follows
#
# Row 0 | Req ID     | Req Name | Req Type     | Req Category    | Set Coverage     | File 1      | File 2 | File 3 | ... |
# Row 1 | Summary    |    -     | types counts | category counts | error count      | error count |  etc   |  etc   | ... |
# Row 2 | REQ_EP_111 |   name   | type         | category        | set test result  | test result |  etc   |  etc   | ... |
# Row 3 | REQ_EP_222 |   name   | type         | category        | set test result  | test result |  etc   |  etc   | ... |
# Row i | REQ_EP_333 |   name   | type         | category        | set test result  | test result |  etc   |  etc   | ... |
#
# The table is constructed as an array of arrays.
#
# The number of rows is: 2 + len(requirments).  The nubmer of columns
# is 4 + len(files). The table is prefilled initialized at these
# dimensions then addressed using array indices
#

# files is an array of filenames
# requirements is a map of Requirement objects keyed by requirement id

# Keep track of the column indices for later updates.
# These match the order of the row appends below. They
# are used by the functions below.
# FIXME - These should be in a class.

idCol         = 0
nameCol       = 1
typeCol       = 2
categoryCol   = 3
fileSetCol    = 4
titleRow      = 0
summaryRow    = 1
firstDataRow  = 2

def get_result_data(files, requirements):

	# Initial table with empty array to store the rows.
	data=[]
	numRows = 2 + len(requirements)
	numCols = 4 + len(files)
	for i in range(0, numRows):
		data.append([])
		data[i] = []
		for j in range(0,numCols):
			data[i].append('')

	# begin initilizing at row zero.
	row = 0;

	# Row 0, title row
	assert titleRow == row
	data[titleRow][idCol] = 'ID'
	data[titleRow][nameCol] = 'Name'
	data[titleRow][typeCol] = 'Type'
	data[titleRow][categoryCol] = 'Category'
	data[titleRow][fileSetCol] = 'File Set<br>Coverage'

	for i in range(0, len(files)):
		file = files[i]
		data[titleRow][fileSetCol+i] = os.path.basename(file)

	# Row 1, summary row (result counts also go on this row but
	# are assigned at the end of this function).
	row = row + 1
	assert summaryRow == row
	data[summaryRow][idCol] = 'Summary'
			
	# Temporary dictionaries to store summary counts and per-file
	# pass/warn/fail result counts.
	categories = {}
	types = {}

	# Temporary dictionaries to store pass/warn/fail result counts per file.
	covers  = {} 
	notes   = {} 
	fails   = {} 
	warns   = {}
	passes  = {}

	# Sort requirements for display
	sortedReqs = requirements.values()
	sortedReqs.sort()

	# The test results...
	assert firstDataRow == row + 1
	for req in sortedReqs:

		# The new row.
		row=row+1

		# Init first five columns: requirment id, name, type
		# and set category, and set coverage
		data[row][idCol]       = req.get_id()
                data[row][nameCol]     = req.get_name()
		data[row][typeCol]     = req.get_type()
		data[row][categoryCol] = req.get_category()

		# update requirement type summary count
		if req.get_type() not in types:
			types[req.get_type()] = 1
		else:
			types[req.get_type()] = types[req.get_type()] + 1

		# update requirement category summary count
		if req.get_category() not in categories:
			categories[req.get_category()] = 1
		else:
			categories[req.get_category()] = categories[req.get_category()] + 1

		# Next, update pass/warn/fail count for each file and
		# insert the test results for requirement "req" into
		# the table for each file. Note, the first column of
		# "files" is the overall set result.

		for i in range(0,len(files)):

			file = files[i]

			# Is there a test result avaiable for this requirement and file?
			if not req.test_result_exists_for_file(file):
				continue

			testResult = req.get_file_test_result_for_file(file)

			data[row][fileSetCol+i] = testResult

			# update summary counts
			if file not in covers:
				covers[file] = 0
			if file not in notes:
				notes[file] = 0
			if file not in fails:
				fails[file] = 0
			if file not in warns:
				warns[file] = 0
			if file not in passes:
				passes[file] = 0

			if testResult == 'Failed':
				fails[file] = fails[file] + 1
			elif testResult == 'Warned':
				warns[file] = warns[file] + 1
			elif testResult == 'Passed':
				passes[file] = passes[file] + 1
			elif testResult == 'Noted':
				notes[file] = notes[file] + 1
			elif testResult == 'Covered':
				covers[file] = covers[file] + 1
			else:
				assert False, "unkown test result"
	
	# Finally, having collected all the types, categories, and
	# results, fill in the summary counts.
	s=''
	keys=types.keys()
	keys.sort()
	for type in keys:
		s = s + str(types[type]) + '  ' + type + '<br>'
	data[summaryRow][typeCol] = s

	s = ''
	keys=categories.keys()
	keys.sort()
	for cat in keys:
		s = s + str(categories[cat]) + '  ' + cat + '<br>'
	data[summaryRow][categoryCol] = s

	if len(files) > 1:
		for i in range(0,len(files)):
			file = files[i]
			data[summaryRow][fileSetCol+i] = \
			      str(covers[file]) + ' covered<br>' \
			    + str(notes[file])  + ' noted<br>'   \
			    + str(passes[file]) + ' passed<br>'  \
			    + str(warns[file])  + ' warned<br>'  \
			    + str(fails[file])  + ' failed<br>'

	return data

# Writes out the table data to an HTML file

# This routing will detect if only a single file is in the set and
# skip the "File Set" column.

def write_html(outputFileName, testResultData, requirements, removedFiles):

	try:
		cfile=open( outputFileName, 'w' )

		cfile.write( '<HTML>' )
		cfile.write( '<HEAD><TITLE>AAF Analyzer Test Results</TITLE></HEAD>' )
		cfile.write( '<BODY>' )

		cfile.write( "<CENTER>" )

		if len(removedFiles) > 0:
			cfile.write( "<B><P>" )
			cfile.write( "The followings files caused the AAFAnalyzer to exit " )
			cfile.write( "abnormally and are not included in the result:</P>" )
			cfile.write( "</P><P>" )
			for removed in removedFiles:
				cfile.write( removed );
				cfile.write( "<BR>" )
			cfile.write( "</P></B>" )

		cfile.write( '<TABLE BORDER="0" CELLSPACING="3" CELLPADDING="1">' )

		for row in range(0, len(testResultData)):

			rowhtml = "";
			if row%2==0:
				rowhtml = '<TR bgcolor="lightgrey">\n'
			else:
				rowhtml = '<TR bgcolor="paleturquoise">\n'
							
			emphasizeNotCovered = False
			if row >= firstDataRow:
				# include the row number to ensure easy reference
				rowhtml = rowhtml + ('<TD>%d</TD>' % (row - firstDataRow + 1))
				id = testResultData[row][0]
				req = requirements[id]
				if not req.get_is_covered_by_test():
					emphasizeNotCovered = True

				# Supress all def requirements and app
				# requirements that are not covered by
				# tests.
				if req.get_type() == 'def' or (req.get_type() == 'app' and not req.get_is_covered_by_test()):
				   continue
			else:
				rowhtml = rowhtml + '<TD></TD>'

			cfile.write( rowhtml );

			for col in range(0, len(testResultData[row])):

				# Detect if there is only one file being reported 
				# and skip the "File Set" column if so.
				if fileSetCol+2 == len(testResultData[row]) and col == fileSetCol:
					continue;

				TD = '<TD'
				if testResultData[row][col] == 'Failed':
					TD = TD + ' bgcolor="#CC0000"'
				elif testResultData[row][col] == 'Warned':
					TD = TD + ' bgcolor="#FFCC00"'
				if col > 0:
					TD = TD + ' NOWRAP=1 ALIGN="center"'
				TD = TD + '>'
				cfile.write(TD)

				if emphasizeNotCovered:
					cfile.write( "<I><FONT color=fuchsia>" )

				cfile.write(testResultData[row][col])

				if emphasizeNotCovered:
					cfile.write( "</FONT></I>" )
				
				cfile.write('</TD>')
			cfile.write('</TR>\n')
		
		cfile.write('</TABLE>')
		cfile.write('<P><I><FONT color=fuchsia>This text style indicates that a requirement is not covered by a test.</FONT></I></P>')
		cfile.write( "</CENTER>" )
		cfile.write( '</BODY>' )
		cfile.write( '</HTML>' )
		cfile.close()
	except IOError, e:
		print >>sys.stderr, "Error writing file: ", e

